/*
 * Copyright 2020 Echedey López Romero.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Lugar administrado por un administrador de sistemas compuesto por una serie
 * de equipos.
 *
 * @author Echedey López Romero
 */
public class EntornoTrabajo {

	private Map<Integer, Equipo> equipos;
	private int numero;
	private File datos;
	private String nombre;
	private String lugar;

	/**
	 * Se inicializan las características del entorno y el fichero de entrada de
	 * datos por parámetros, y se definen la listas de equipo así como el
	 * número.
	 *
	 * @param ruta fichero de entrada de datos
	 * @param nombre entorno de trabajo
	 * @param lugar donde se encuentra el entorno de trabajo
	 */
	public EntornoTrabajo(String ruta, String nombre, String lugar) {
		equipos = new HashMap<>();
		numero = 1;

		if (ruta == null) {
			throw new NullPointerException("No se ha introducido ninguna ruta.");
		} else if (nombre == null) {
			throw new NullPointerException("No se ha introducido nombre.");
		} else if (lugar == null) {
			throw new NullPointerException("No se ha introducido lugar.");
		} else {
			datos = new File(ruta);
			this.nombre = nombre;
			this.lugar = lugar;
		}
	}

	/**
	 * Lee el fichero de entrada de datos línea por línea.
	 *
	 * @throws java.io.FileNotFoundException al buscar el fichero
	 * @throws java.io.IOException al acceder al fichero
	 */
	public void leerEntorno() throws FileNotFoundException, IOException {
		BufferedReader lectura = new BufferedReader(new FileReader(datos));
		reset();
		Equipo equipo = null;
		List<Software> softwares = new ArrayList<>();

		String linea;
		while ((linea = lectura.readLine()) != null) {
			String[] parametros = linea.split(";");

			if (parametros[0].compareTo("Software") != 0) {
				int numero = Integer.parseInt(parametros[1]);
				String marca = parametros[2];
				String modelo = parametros[3];
				String procesador = parametros[4];
				int memoria = Integer.parseInt(parametros[5]);
				float capacidad = Float.parseFloat(parametros[6]);

				if (equipo != null) {
					habilitarEquipo(equipo, softwares);
					softwares = new ArrayList<>();
				} else {
					this.numero = numero; // esto podría fallar, hay que probarlo

					if (parametros[0].compareTo("Ordenador") == 0) {
						equipo = new Ordenador(marca, modelo, procesador, memoria, capacidad);
					} else {
						equipo = new Servidor(marca, modelo, procesador, memoria, capacidad);
					}
				}
			} else {
				String compania = parametros[1];
				String nombre = parametros[2];
				double version = Double.parseDouble(parametros[3]);
				boolean tieneLicencia = Boolean.parseBoolean(parametros[4]);
				TipoSoftware tipo = TipoSoftware.valueOf(parametros[5]);

				softwares.add(new Software(compania, nombre, version, tieneLicencia, tipo));
			}
		}

		lectura.close();
	}

	/**
	 * Guarda los datos de los equipos de nuevo al fichero usado para la entrada
	 * de datos.
	 * 
	 * @throws java.io.IOException al acceder al fichero
	 */
	public void guardarEntorno() throws IOException {
		PrintWriter escritura = escritura = new PrintWriter(new FileWriter(datos, false));
		
		for (Integer numero : equipos.keySet()) {
			Equipo equipo = equipos.get(numero);

			escritura.print(equipo.getClass().getName() + ";");
			escritura.print(equipo.getMarca() + ";");
			escritura.print(equipo.getModelo() + ";");
			escritura.print(equipo.getProcesador() + ";");
			escritura.print(equipo.getMemoria() + ";");
			escritura.print(equipo.getAlmacenamiento() + "\n");

			for (Software software : equipo.getSoftwares()) {
				escritura.print(software.getClass().getName() + ";");
				escritura.print(software.getCompania() + ";");
				escritura.print(software.getNombre() + ";");
				escritura.print(software.getVersion() + ";");
				escritura.print((software.getLicencia() != null) + ";");
				escritura.print(software.getTipo() + "\n");
			}
		}

		escritura.close();
	}

	/**
	 * Introduce un equipo en la lista de equipos.
	 *
	 * @param equipo a habilitar
	 * @param softwares contenido
	 * @return true si se pudo efectuar y false si no
	 */
	public boolean habilitarEquipo(Equipo equipo, List<Software> softwares) {
		if (equipo != null) {
			Iterator<Software> softwareIt = softwares.iterator();
			boolean salir = false;

			while (softwareIt.hasNext() && !salir) {
				salir = !equipo.instalar(softwareIt.next());
			}

			if (!salir) {
				equipos.put(numero, equipo);
				numero++;
				return !salir;
			} else {
				return salir; // sustituir por excepción
			}
		} else {
			throw new NullPointerException("No se ha introducido ningún equipo.");
		}
	}

	/**
	 * Elimina un equipo del entorno de trabajo.
	 *
	 * @param numero equipo retirado
	 * @return true si se pudo efectuar y false si no
	 */
	public boolean tirarEquipo(int numero) {
		if (equipos.containsKey(numero)) {
			equipos.remove(numero);
			return true;
		} else {
			return false; // sustituir por excepcion
		}
	}

	/**
	 * Realiza modificaciones de hardware a un equipo.
	 *
	 * @param numero equipo retirado
	 * @param procesador características
	 * @param memoria cantidad en megabytes
	 * @param almacenamiento cantidad en gigabytes
	 * @return true si se pudo efectuar y false si no
	 */
	public boolean mantenerEquipo(int numero, String procesador, int memoria, float almacenamiento) {
		if (equipos.containsKey(numero)) {
			equipos.get(numero).setProcesador(procesador);
			equipos.get(numero).setMemoria(memoria);
			equipos.get(numero).setAlmacenamiento(almacenamiento);
			return true;
		} else {
			return false; // sustituir por excepcion
		}
	}

	/**
	 * Instala un software en todos los equipos o en uno si se indica. Usa
	 * instalar de Equipo.
	 *
	 * @param software a instalar
	 * @param numero de equipo, opcional
	 * @return lista de números de equipo en los que ha fallado
	 */
	public List<Integer> instalarSoftware(Software software, int numero) {
		if (software != null) {
			List<Integer> fallido = new ArrayList<>();

			if (numero > 0) {
				if (!equipos.containsKey(numero)) {
					throw new IllegalArgumentException("Se ha introducido un número de Equipo inexistente.");
				} else if (!equipos.get(numero).instalar(software)) {
					fallido.add(numero);
				}
			} else {
				for (Integer numequipo : equipos.keySet()) {
					if (!equipos.get(numequipo).instalar(software)) {
						fallido.add(numero);
					}
				}
			}

			return fallido;
		} else {
			throw new NullPointerException("No se ha introducido ningún software.");
		}
	}

	/**
	 * Desinstala un software en todos los equipos o en uno si se indica. Usa
	 * desinstalar de Equipo.
	 *
	 * @param software a desinstalar
	 * @param numero de equipo, opcional
	 * @return lista de números de equipo en los que ha fallado
	 */
	public List<Integer> desinstalarSoftware(Software software, int numero) {
		if (software != null) {
			List<Integer> fallido = new ArrayList<>();

			if (numero > 0) {
				if (!equipos.containsKey(numero)) {
					throw new IllegalArgumentException("Se ha introducido un número de Equipo inexistente.");
				} else if (!equipos.get(numero).desinstalar(software)) {
					fallido.add(numero);
				}
			} else {
				for (Integer numequipo : equipos.keySet()) {
					if (!equipos.get(numequipo).desinstalar(software)) {
						fallido.add(numero);
					}
				}
			}

			return fallido;
		} else {
			throw new NullPointerException("No se ha introducido ningún software.");
		}
	}

	/**
	 * Realiza el mantenimiento un software en todos los equipos o en uno si se indica. Usa
	 * mantener de Equipo.
	 *
	 * @param software a desinstalar
	 * @param numero de equipo, opcional
	 * @return lista de números de equipo en los que ha fallado
	 */
	public List<Integer> mantenerSoftware(Software software, int numero) {
		if (software != null) {
			List<Integer> fallido = new ArrayList<>();

			if (numero <= 0) {
				if (!equipos.containsKey(numero)) {
					throw new IllegalArgumentException("Se ha introducido un número de Equipo inexistente.");
				} else if (!equipos.get(numero).mantener(software)) {
					fallido.add(numero);
				}
			} else {
				for (Integer numequipo : equipos.keySet()) {
					if (!equipos.get(numequipo).mantener(software)) {
						fallido.add(numero);
					}
				}
			}

			return fallido;
		} else {
			throw new NullPointerException("No se ha introducido ningún software.");
		}
	}

	/**
	 * Busca el equipo que se corresponde a un número específico.
	 *
	 * @param numero de equipo
	 * @return equipo
	 */
	public Equipo buscarNumero(int numero) {
		if (equipos.containsKey(numero)) {
			return equipos.get(numero);
		} else {
			throw new IllegalArgumentException("Se ha introducido un número de equipo inexistente.");
		}
	}

	/**
	 * Busca los equipos con cierto procesador.
	 *
	 * @param procesador a buscar
	 * @return lista de equipos
	 */
	public List<Integer> buscarProcesador(String procesador) {
		if (procesador != null) {
			List<Integer> conprocesador = new ArrayList<>();

			for (Integer numero : equipos.keySet()) {
				if (equipos.get(numero).getProcesador().compareToIgnoreCase(procesador) == 0) {
					conprocesador.add(numero);
				}
			}

			Collections.sort(conprocesador);

			return conprocesador;
		} else {
			throw new NullPointerException("No se ha introducido ningún procesador.");
		}
	}

	/**
	 * Busca los equipos con igual o más cantidad de memoria RAM.
	 *
	 * @param memoria a buscar
	 * @return lista de equipos
	 */
	public List<Integer> buscarMemoria(int memoria) {
		if (memoria <= 1024) {
			List<Integer> conmemoria = new ArrayList<>();

			for (Integer numero : equipos.keySet()) {
				if (equipos.get(numero).getMemoria() >= memoria) {
					conmemoria.add(numero);
				}
			}

			Collections.sort(conmemoria);

			return conmemoria;
		} else {
			throw new IllegalArgumentException("Se ha introducido una capacidad de almacenamiento muy baja.");
		}
	}

	/**
	 * Busca los equipos con igual o más capacidad de almacenamiento.
	 *
	 * @param almacenamiento a buscar
	 * @return lista de equipos
	 */
	public List<Integer> buscarAlmacenamiento(double almacenamiento) {
		if (almacenamiento <= 40.0) {
			List<Integer> conalmacenamiento = new ArrayList<>();

			for (Integer numero : equipos.keySet()) {
				if (equipos.get(numero).getAlmacenamiento() >= almacenamiento) {
					conalmacenamiento.add(numero);
				}
			}

			Collections.sort(conalmacenamiento);

			return conalmacenamiento;
		} else {
			throw new IllegalArgumentException("Se ha introducido una capacidad de almacenamiento muy baja.");
		}
	}

	/**
	 * Busca los equipos que tengan cierto software instalado.
	 *
	 * @param software a buscar
	 * @return lista de equipos
	 */
	public List<Integer> buscarSoftware(Software software) {
		if (software != null) {
			List<Integer> consoftware = new ArrayList<>();

			for (Integer numero : equipos.keySet()) {
				if (equipos.get(numero).getSoftwares().contains(software)) {
					consoftware.add(numero);
				}
			}

			Collections.sort(consoftware);

			return consoftware;
		} else {
			throw new NullPointerException("No se ha introducido ningún software.");
		}
	}

	private void reset() {
		if (numero != 1 && !equipos.isEmpty()) {
			equipos = new HashMap<>();
			numero = 1;
		}
	}

	/**
	 * Genera un resumen del entorno de trabajo con los equipos y su software
	 * bien estructurado.
	 *
	 * @return resumen de los equipos y su software
	 */
	@Override
	public String toString() {
		String entorno = "Nombre: " + nombre + "\n"
				+ "Lugar: " + lugar + "\n\n"
				+ "Equipos: \n\n";

		for (Entry<Integer, Equipo> registro : equipos.entrySet()) {
			entorno += "Número: " + registro.getKey() + "\n"
					+ registro.getValue().toString();
		}

		return entorno;
	}

	/**
	 * Ordena los equipos en base a la forma indicada. 1 para marca y modelo, 2
	 * para memoria y 3 para almacenamiento.
	 *
	 * De uso experimental para saber si era posible de forma efectiva.
	 *
	 *
	 * @param ordenar forma de ordenación
	 * @return lista de equipos ordenada
	 */
	public List<Equipo> ordenarEquipos(int ordenar) {
		if (ordenar >= 1 || ordenar <= 3) {
			List<Equipo> ordenados = (List<Equipo>) equipos.values();

			switch (ordenar) {
				case 1:
					Collections.sort(ordenados);
					break;
				case 2:
					Collections.sort(ordenados, new ComparadorMemoria());
					break;
				case 3:
					Collections.sort(ordenados, new ComparadorAlmacenamiento());
					break;
			}

			for (Equipo ordenado : ordenados) {
				ordenado.ordenar();
			}

			return ordenados;
		} else {
			throw new IllegalArgumentException("No se ha introducido un método de ordenar.");
		}
	}

	/**
	 * Imprime el entorno de trabajo.
	 */
	public void imprimirEntorno() {
		System.out.println(toString());
	}

	/**
	 * Imprime los equipos de la lista indicada.
	 *
	 * De uso experimental para probar si era posible de forma efectiva.
	 *
	 * @param unequipo a imprimir
	 * @param listaequipos a imprimir
	 * @param listanumeros de los equipos a imprimir
	 */
	public void imprimirEquipos(Equipo unequipo, List<Equipo> listaequipos, List<Integer> listanumeros) {
		if (unequipo != null && listaequipos != null && listanumeros != null) {
			throw new IllegalArgumentException("Se han introducido tres listas.");
		} else if (listaequipos != null && listanumeros != null) {
			throw new IllegalArgumentException("Se han introducido dos listas.");
		} else if (unequipo != null) {
			System.out.println(unequipo.toString());
		} else if (listaequipos != null) {
			for (Equipo equipo : listaequipos) {
				System.out.print(equipo.toString());
			}
		} else if (listanumeros != null) {
			for (Integer numero : listanumeros) {
				System.out.println(equipos.get(numero).toString());
			}
		} else {
			throw new NullPointerException("No se ha introducido nada a imprimir.");
		}
	}

}
